{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Install Feast on Kubernetes with the Feast Operator\n",
    "## Objective\n",
    "\n",
    "Provide a reference implementation of a runbook to deploy a Feast environment on a Kubernetes cluster using the [Feast Operator](../../infra/feast-operator/)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Prerequisites\n",
    "* Kubernetes Cluster\n",
    "* [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl) Kubernetes CLI tool."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Install Prerequisites\n",
    "\n",
    "The following commands install and configure all the prerequisites on a MacOS environment. You can find the\n",
    "equivalent instructions on the offical documentation pages:\n",
    "* Install the `kubectl` cli.\n",
    "* Install Kubernetes and Container runtime (e.g. [Colima](https://github.com/abiosoft/colima)).\n",
    "  * Alternatively, authenticate to an existing Kubernetes or OpenShift cluster.\n",
    "  \n",
    "```bash\n",
    "brew install colima kubectl\n",
    "colima start -r containerd -k -m 3 -d 100 -c 2 --cpu-type max -a x86_64\n",
    "colima list\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "namespace/feast created\n",
      "Context \"colima\" modified.\n"
     ]
    }
   ],
   "source": [
    "!kubectl create ns feast\n",
    "!kubectl config set-context --current --namespace feast"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Validate the cluster setup:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NAME    STATUS   AGE\n",
      "feast   Active   3s\n"
     ]
    }
   ],
   "source": [
    "!kubectl get ns feast"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Deployment Architecture\n",
    "The primary objective of this runbook is to guide the deployment of Feast services on a Kubernetes cluster, using the `postgres` template to set up a basic feature store.\n",
    "\n",
    "In this notebook, we will deploy a distributed topology of Feast services, which includes:\n",
    "\n",
    "* `Registry Server`: Handles metadata storage for feature definitions.\n",
    "* `Online Store Server`: Uses the `Registry Server` to query metadata and is responsible for low-latency serving of features.\n",
    "* `Offline Store Server`: Uses the `Registry Server` to query metadata and provides access to batch data for historical feature retrieval.\n",
    "\n",
    "Each service is backed by a `PostgreSQL` database, which is also deployed within the same cluster."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup Postgresql and Redis\n",
    "Apply the included [postgres](postgres.yaml) & [redis](redis.yaml) deployments to run simple databases."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "secret/postgres-secret created\n",
      "deployment.apps/postgres created\n",
      "service/postgres created\n",
      "deployment.apps/redis created\n",
      "service/redis created\n",
      "deployment.apps/redis condition met\n",
      "deployment.apps/postgres condition met\n"
     ]
    }
   ],
   "source": [
    "!kubectl apply -f postgres.yaml -f redis.yaml\n",
    "!kubectl wait --for=condition=available --timeout=5m deployment/redis\n",
    "!kubectl wait --for=condition=available --timeout=5m deployment/postgres"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NAME                           READY   STATUS    RESTARTS   AGE\n",
      "pod/postgres-ff8d4cf48-rvp86   1/1     Running   0          71s\n",
      "pod/redis-b4756b75d-m5l96      1/1     Running   0          70s\n",
      "\n",
      "NAME               TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE\n",
      "service/postgres   ClusterIP   10.43.193.169   <none>        5432/TCP   71s\n",
      "service/redis      ClusterIP   10.43.64.8      <none>        6379/TCP   69s\n",
      "\n",
      "NAME                       READY   UP-TO-DATE   AVAILABLE   AGE\n",
      "deployment.apps/postgres   1/1     1            1           71s\n",
      "deployment.apps/redis      1/1     1            1           70s\n",
      "\n",
      "NAME                                 DESIRED   CURRENT   READY   AGE\n",
      "replicaset.apps/postgres-ff8d4cf48   1         1         1       71s\n",
      "replicaset.apps/redis-b4756b75d      1         1         1       70s\n"
     ]
    }
   ],
   "source": [
    "!kubectl get all"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Install the Feast Operator"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "namespace/feast-operator-system created\n",
      "customresourcedefinition.apiextensions.k8s.io/featurestores.feast.dev created\n",
      "serviceaccount/feast-operator-controller-manager created\n",
      "role.rbac.authorization.k8s.io/feast-operator-leader-election-role created\n",
      "clusterrole.rbac.authorization.k8s.io/feast-operator-featurestore-editor-role created\n",
      "clusterrole.rbac.authorization.k8s.io/feast-operator-featurestore-viewer-role created\n",
      "clusterrole.rbac.authorization.k8s.io/feast-operator-manager-role created\n",
      "clusterrole.rbac.authorization.k8s.io/feast-operator-metrics-auth-role created\n",
      "clusterrole.rbac.authorization.k8s.io/feast-operator-metrics-reader created\n",
      "rolebinding.rbac.authorization.k8s.io/feast-operator-leader-election-rolebinding created\n",
      "clusterrolebinding.rbac.authorization.k8s.io/feast-operator-manager-rolebinding created\n",
      "clusterrolebinding.rbac.authorization.k8s.io/feast-operator-metrics-auth-rolebinding created\n",
      "service/feast-operator-controller-manager-metrics-service created\n",
      "deployment.apps/feast-operator-controller-manager created\n",
      "deployment.apps/feast-operator-controller-manager condition met\n"
     ]
    }
   ],
   "source": [
    "## Use this install command from a release branch (e.g. 'v0.43-branch')\n",
    "!kubectl apply -f ../../infra/feast-operator/dist/install.yaml\n",
    "\n",
    "## OR, for the latest code/builds, use one the following commands from the 'master' branch\n",
    "# !make -C ../../infra/feast-operator install deploy IMG=quay.io/feastdev-ci/feast-operator:develop FS_IMG=quay.io/feastdev-ci/feature-server:develop\n",
    "# !make -C ../../infra/feast-operator install deploy IMG=quay.io/feastdev-ci/feast-operator:$(git rev-parse HEAD) FS_IMG=quay.io/feastdev-ci/feature-server:$(git rev-parse HEAD)\n",
    "\n",
    "!kubectl wait --for=condition=available --timeout=5m deployment/feast-operator-controller-manager -n feast-operator-system"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Install the Feast services via FeatureStore CR\n",
    "Next, we'll use the running Feast Operator to install the feast services. Apply the included [reference deployment](feast.yaml) to install and configure Feast."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "secret/feast-data-stores created\n",
      "featurestore.feast.dev/example created\n"
     ]
    }
   ],
   "source": [
    "!kubectl apply -f feast.yaml"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Validate the running FeatureStore deployment\n",
    "Validate the deployment status."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NAME                                READY   STATUS     RESTARTS   AGE\n",
      "pod/feast-example-6c6b58474-n62rg   0/1     Init:0/1   0          4s\n",
      "pod/postgres-ff8d4cf48-rvp86        1/1     Running    0          3m23s\n",
      "pod/redis-b4756b75d-m5l96           1/1     Running    0          3m22s\n",
      "\n",
      "NAME                           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE\n",
      "service/feast-example-online   ClusterIP   10.43.175.253   <none>        80/TCP     6s\n",
      "service/postgres               ClusterIP   10.43.193.169   <none>        5432/TCP   3m23s\n",
      "service/redis                  ClusterIP   10.43.64.8      <none>        6379/TCP   3m21s\n",
      "\n",
      "NAME                            READY   UP-TO-DATE   AVAILABLE   AGE\n",
      "deployment.apps/feast-example   0/1     1            0           5s\n",
      "deployment.apps/postgres        1/1     1            1           3m23s\n",
      "deployment.apps/redis           1/1     1            1           3m22s\n",
      "\n",
      "NAME                                      DESIRED   CURRENT   READY   AGE\n",
      "replicaset.apps/feast-example-6c6b58474   1         1         0       5s\n",
      "replicaset.apps/postgres-ff8d4cf48        1         1         1       3m23s\n",
      "replicaset.apps/redis-b4756b75d           1         1         1       3m22s\n",
      "\n",
      "NAME                          SCHEDULE   TIMEZONE   SUSPEND   ACTIVE   LAST SCHEDULE   AGE\n",
      "cronjob.batch/feast-example   @yearly    <none>     True      0        <none>          3s\n",
      "deployment.apps/feast-example condition met\n"
     ]
    }
   ],
   "source": [
    "!kubectl get all\n",
    "!kubectl wait --for=condition=available --timeout=8m deployment/feast-example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Validate that the FeatureStore CR is in a `Ready` state."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "NAME      STATUS   AGE\n",
      "example   Ready    7m40s\n"
     ]
    }
   ],
   "source": [
    "!kubectl get feast"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Verify that the DB includes the expected tables."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "                List of relations\n",
      " Schema |          Name           | Type  | Owner \n",
      "--------+-------------------------+-------+-------\n",
      " public | data_sources            | table | feast\n",
      " public | entities                | table | feast\n",
      " public | feast_metadata          | table | feast\n",
      " public | feature_services        | table | feast\n",
      " public | feature_views           | table | feast\n",
      " public | managed_infra           | table | feast\n",
      " public | on_demand_feature_views | table | feast\n",
      " public | permissions             | table | feast\n",
      " public | projects                | table | feast\n",
      " public | saved_datasets          | table | feast\n",
      " public | stream_feature_views    | table | feast\n",
      " public | validation_references   | table | feast\n",
      "(12 rows)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "!kubectl exec deploy/postgres -- psql -h localhost -U feast feast -c '\\dt'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, let's verify the feast version."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Feast SDK Version: \"0.1.dev1+gcc1fcad.d20250403\"\n"
     ]
    }
   ],
   "source": [
    "!kubectl exec deployment/feast-example -itc online -- feast version"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
